#!/usr/bin/python2.4
#
#Copyright 2009 Google Inc.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
#

"""AppEngine db related utilities for Digital Content Wizard.

  This module has all db model objects, which represent a table in AppEngine
  db. It also provides a class DbHandler. This class primarily interacts with
  db.
"""

import logging
import time
import zlib
import constants
from google.appengine import runtime
from google.appengine.ext import db
import digital_content_wizard_exceptions


class PageParent(db.Model):
  """Provides single root for all pages of one version.

  This class provides parent for all the pages of one version. It has a
  single attribute version_id.
  """
  version_id = db.IntegerProperty()


class Page(db.Model):
  """This class represents a Page in db.

  This class holds all the versions of all html pages of the website. It also
  holds sitemap.xml.

  Attributes:
  version_id: Version id specifies the different versions of an application.
  Version is not AppEngine version but different versions of web application
  generated using different trixsheets.
  html_url: Stores the url, which when requested, this page is to be
  displayed.
  html_content: Stores the html content of the page.
  """

  version_id = db.IntegerProperty()
  html_url = db.StringProperty()
  html_content = db.BlobProperty()


class AppVersion(db.Model):
  """This class describes the version of the app.

  It describes which type of version is this.

  Attributes:
  version_id: Version id
  version_type: Version_type identifies whether the version is the published
  version, current version or none of these:
                1: published version
                2: current version
                3. ordinary version
  levels: Number of page tree levels
  page_tree: Compressed blob representing page tree in the form of json object.
  """
  version_id = db.IntegerProperty()
  version_type = db.IntegerProperty()
  levels = db.IntegerProperty()
  page_tree = db.BlobProperty()


class NonVersionedData(db.Model):
  """This class describes non-versioned data for the app.

  It is similar to the GlobalData except that there is no version associated
  with the data stored here.

  Attributes:
  data_name: Name of the attribute.
  data_value: Value of the attribute.
  """
  data_name = db.StringProperty()
  data_value = db.StringProperty()


class Image(db.Model):
  """This class stores the images in db.

  Images are not versioned.

  Attributes:
  image_name: Image name.
  image_data: Blob of image.
  """
  image_name = db.StringProperty()
  image_data = db.BlobProperty()


class AppStatus(db.Model):
  """This class stores the status of creating a new version.

  There are 7 steps involved in creating a new version. This class stores the
  information as to what step is currently going. This is useful for resume
  facility.

  Attributes:
  status: Integer indicating the latest step that has been completed.
  """
  status = db.StringProperty()


class Metadata(db.Model):
  """This class represents the metadata.

  Metadata is stored in db row by row.

  Attributes:
  row_id: Row number.
  id: For video this is youtube id and for pdf documents this is rain encrypted
  id.
  version_id: Version id.
  sorting_column1: Value of first sorting column.
  sorting_column2: Value of second sorting column.
  sorting_column3: Value of third sorting column.
  title: Title of the video/document.
  rest_metadata: Tab separated values for rest of the columns.
  """
  row_id = db.IntegerProperty()
  id = db.StringProperty()
  version_id = db.IntegerProperty()
  sorting_column1 = db.StringProperty()
  sorting_column2 = db.StringProperty()
  sorting_column3 = db.StringProperty()
  title = db.StringProperty()
  rest_metadata = db.TextProperty()


class GlobalData(db.Model):
  """This class stores the global data for the website.

  Global data is the metadata that doesn't change with the every property.
  It usually consists of about us, contact us, logo images, etc.

  Attributes:
  row_id: Row number.
  version_id: Version id.
  type: Type of the global data attribute.
  value: Value of the attribute.
  variable: Variable name that is used in html to refer to this attribute.
  """
  row_id = db.IntegerProperty()
  version_id = db.IntegerProperty()
  type = db.StringProperty()
  value = db.TextProperty()
  variable = db.StringProperty()


class DbHandler(object):
  """This class provides methods to interact with AppEngine db.

  This class implements following categories of functions:
  Generic Function: Not related to any specific Model.
  Functions specific to Page, Image, Status, PageParent, Version, Metadata,
  GlobalData.
  """

#########################################################################
#                       Generic Functions
#########################################################################
  def AddToDb(self, db_object):
    """This function adds given object to the database.

    Args:
      db_object: Object to be added to db. usually db.Model instance or list of
      db.Model instances.
    Returns:
      True if successful; False if DeadlineExceededError.

    """
    try:
      db.put(db_object)
      return True
    except runtime.DeadlineExceededError, e:
      logging.error(e)
      return False

########################################################################
#                     Non-versioned data  Related Functions
########################################################################
  def AddNonVersionedData(self, data_name, data_value):
    """Add an attribute-value pair to NonVersionedData table.

    Args:
      data_name: String name of the attribute.
      data_value: String value of the attribute.

    Returns:
      True if successful in adding to db; False if db error occurs.
    """
    try:
      attribute = NonVersionedData()
      attribute.data_name = data_name
      attribute.data_value = data_value
      attribute.put()
    except db.Error, e:
      logging.error(e)
      return False
    return True

  def GetNonVersionedData(self, data_name):
    """Returns the value of the specified attribute name.

    Args:
      data_name: String name of the attribute.

    Returns:
      Attribute value if it exists in NonVersionedData table. None otherwise.
    """
    try:
      query = NonVersionedData.gql('WHERE data_name = :1', data_name)
      result = query.fetch(1)
      if result:
        return result[0].data_value
      return None
    except db.Error, e:
      logging.error(e)
      return None

########################################################################
#                     Image  Related Functions
########################################################################
  def AddImage(self, image_name, image_data):
    """Add image data to Image table.

    Args:
      image_name: Image url string.
      image_data: Binary image blob.

    Returns:
      True if successful in adding to db; False if db error occurs.
    """
    try:
      # Get the current version
      logging.info('Trying to add image %s', image_name)
      query = Image.gql('WHERE image_name = :1', image_name)
      result = query.fetch(1)
      if result:
        image = result[0]
      else:
        # No image with this name found. So create a new image.
        image = Image()
        image.image_name = image_name

      image.image_data = image_data
      image.put()
      logging.debug('Image %s added successfully', image_name)
    except db.Error, e:
      logging.error(e)
      return False
    return True

  def GetImage(self, image_name):
    """Returns the blob of the specified image.

    Args:
      image_name: Image url string.

    Returns:
      Image blob if image exists in Image table. None otherwise.
    """
    try:
      logging.debug('Trying to get image %s', image_name)
      query = Image.gql('WHERE image_name = :1', image_name)
      result = query.fetch(1)
      if result:
        logging.debug('Got the image data')
        return result[0].image_data
      return None
    except db.Error, e:
      logging.error(e)
      return None

######################################################################
#                  Status Related Functions
######################################################################
  def GetStatus(self):
    """Returns the status of the newly created app.

    This is used to detect whether the version creation should be resumed in
    case of pdf one-by-one upload.

    Returns:
      Status String.
    """
    try:
      query = db.GqlQuery('SELECT * FROM AppStatus')
      result = query.fetch(1)
      if result:
        return result[0].status
      return 0
    except db.Error, e:
      logging.error(e)
      return 0

  def RemoveStatus(self):
    """Removes the status from db."""
    try:
      query = db.GqlQuery('SELECT * FROM AppStatus')
      result = query.fetch(1)
      if result:
        result[0].delete()
    except db.Error, e:
      logging.error(e)

  def UpdateStatus(self, status):
    """Updates the status of the new version.

    Args:
      status: New status string.

    Returns:
      True if successful in updating db; False if db error occurs.
    """
    try:
      query = db.GqlQuery('SELECT * FROM AppStatus')
      result = query.fetch(1)
      if result:
        entry = result[0]
      else:
        entry = AppStatus()
      entry.status = status
      entry.put()
      return True
    except db.Error, e:
      logging.error(e)
      return False

####################################################################
#                 Version Related Functions
####################################################################
  def GetNewVersionId(self):
    """Returns new version id.

    It checks what is the highest version number and returns max_version + 1.

    Returns:
      New version id number.
    """
    try:
      # Get all the versions of the given app
      result = db.GqlQuery('SELECT * FROM  AppVersion')
      # Get max value of version
      max_version = 0
      for version in result:
        if version.version_id > max_version:
          max_version = version.version_id
      logging.debug('new version id = %d', (max_version + 1))
      return max_version + 1
    except db.Error, e:
      logging.error(e)
      return -1

  def CreateNewVersion(self):
    """Creates new version. It calls GetNewVersionId to get new id.

    Returns:
      New version id number.
    """
    try:
      # Check if current version is available
      query = AppVersion.gql('WHERE version_type = 2')
      result = query.fetch(1)
      if result:
        # Make all current versions not current
        current_version = result[0]
        current_version.version_type = 3
        current_version.put()

      # Create new version instance
      current_version = AppVersion()
      current_version.version_id = self.GetNewVersionId()
      # Make this current version
      current_version.version_type = 2
      if current_version.version_id == -1:
        return -1
      else:
        current_version.put()

      # Return the id of the newly created version
      return current_version.version_id
    except db.Error, e:
      logging.error(e)
      return -1

  def UnpublishVersion(self):
    """Unpublishes the currently published version.

    Returns:
      True if successful; False if db.Error.
    """
    try:
      # Get published version
      query = AppVersion.gql('WHERE version_type = 1')
      result = query.fetch(1)
      if result:
        version = result[0]
        version.version_type = 3
        version.put()
      return True
    except db.Error, e:
      logging.error(e)
      return False

  def PublishVersion(self, version_id=None):
    """Publishes the specified version.

    This method publishes the specified version and unpublishes the currently
    published version.

    Args:
      version_id: Version id number to be published.

    Returns:
      status: True if successful in publishing the version.
      version_id: Published version if successful; None otherwise.
    """
    try:
      logging.info('Trying to publish version')
      # Get the current version or specified version
      if version_id:
        query = AppVersion.gql('WHERE version_id = :1', version_id)
      else:
        query = AppVersion.gql('WHERE version_type = 2')

      result = query.fetch(1)
      if result:
        version = result[0]
        # Unpublish currently published version
        logging.info('Trying to unpublish currently published version')
        self.UnpublishVersion()
        logging.info('Unpublished current version')
        # Make current version published
        version.version_type = 1
        version.put()
        return (True, version.version_id)
    except db.Error, e:
      logging.error(e)
      return (False, None)
    return (False, None)

  def RemoveVersion(self, version_id):
    """Removes the specified version.

    It does the following:
        1. Removes the version entry from AppVersion.
        2. Removes 300 Pages from Page table (This API is called recursively as
        long as RemovePages function returns True).
        If still more pages are remaining, it returns False.
        If all pages are removed, it removes GlobalData.
        Then it removes 300 entries from metadata same way as Pages.
        In the end it removes the version entry from PageParent table and
        AppStatus table.

    Args:
      version_id: Version id number to be published.

    Returns:
      False: If either more Pages or more Metadata entries are remaining.
      True: Entries for this version from all the tables are removed.
    """
    try:
      logging.info('Trying to remove version %d', version_id)
      # Remove the version entry from AppVersion
      query = AppVersion.gql('WHERE version_id = :1', version_id)
      result = query.fetch(1)
      if result:
        result[0].delete()
        logging.debug('Deleted from AppVersion')
      status = self.RemovePages(version_id)
      if not status:
        return status
      logging.debug('Deleted all the pages')
      self.RemoveGlobalData(version_id)
      logging.debug('Deleted all global data')
      status = self.RemoveMetadata(version_id)
      if not status:
        return status
      logging.debug('Deleted all meta data')
      self.RemoveParent(version_id)
      logging.debug('Deleted page parent')
      self.RemoveStatus()
      logging.debug('Deleted status')
      logging.info('version %s removed successfully', str(version_id))
      return status
    except db.Error, e:
      logging.error(e)
      return False

  def GetAllVersions(self):
    """Returns the array of all the version ids.

    Returns:
      Array containing all the version ids.
    """
    result = db.GqlQuery('SELECT * FROM AppVersion')
    versions = []
    for version in result:
      versions.append(version.version_id)
    return versions

  def GetPublishedVersion(self):
    """Returns id of the published version.

    Returns:
      id String of the published version.
      None if no version is published/db error.
    """
    try:
      query = AppVersion.gql('WHERE version_type = 1')
      result = query.fetch(1)
      if result:
        return result[0].version_id
      return None
    except db.Error, e:
      logging.error(e)
      return None

  def GetLatestVersion(self):
    """Returns the laest version.

    It returns the version with highest version number i.e. recently created
    version.

    Returns:
      id string of the recently created version.
    """
    try:
      result = AppVersion.all()
      latest_version = 0
      for version in result:
        if version.version_id > latest_version:
          latest_version = version.version_id
      return latest_version
    except db.Error, e:
      logging.error(e)
      return -1

  def UpdateNumberOfLevels(self, version_id, levels):
    """Updates the levels attribute of the specified version.

    Args:
      version_id: Version id number.
      levels: Number of levels.
    Returns:
      status: True if successful in updating levels for the specified version.
    """
    try:
      query = AppVersion.gql('WHERE version_id = :1', version_id)
      result = query.fetch(1)
      if result:
        app_version = result[0]
        app_version.levels = levels
        app_version.put()
        return True
      else:
        logging.error('version %d not found', version_id)
        return False
    except db.Error, e:
      logging.error(e)
      return False

  def GetNumberOfLevels(self, version_id):
    """Returns the levels attribute of the specified version.

    Args:
      version_id: Version id number.
    Returns:
      Number of levels of the page tree of the specified version.
    """
    try:
      query = AppVersion.gql('WHERE version_id = :1', version_id)
      result = query.fetch(1)
      if result:
        return result[0].levels
      else:
        logging.error('version %d not found', version_id)
        return -1
    except db.Error, e:
      logging.error(e)
      return -1

  def UpdatePageTree(self, version_id, page_tree):
    """Updates the page_tree attribute of the specified version.

    Args:
      version_id: Version id number.
      page_tree: Blob of page tree json.
    Returns:
      status: True if successful in updating page tree for the specified
      version.
    """
    try:
      query = AppVersion.gql('WHERE version_id = :1', version_id)
      result = query.fetch(1)
      if result:
        app_version = result[0]
        app_version.page_tree = zlib.compress(str(page_tree))
        app_version.put()
        return True
      else:
        logging.error('version %d not found', version_id)
        return False
    except db.Error, e:
      logging.error(e)
      raise digital_content_wizard_exceptions.DbException(
          'Error while updating left navigation tree.\n' + str(e))

  def GetPageTree(self, version_id):
    """Returns the page_tree attribute of the specified version.

    Args:
      version_id: Version id number.
    Returns:
      Page tree blob of the specified version.
    """
    try:
      query = AppVersion.gql('WHERE version_id = :1', version_id)
      result = query.fetch(1)
      if result:
        return zlib.decompress(result[0].page_tree)
      else:
        logging.error('version %d not found', version_id)
        return -1
    except db.Error, e:
      logging.error(e)
      return -1

#############################################################################
#                     Page Related Functions
#############################################################################
  def _AddPages(self, version_id, page_map):
    """Function internal to DbHandler class which adds pages to Page table.

    While adding page content to db, content is first compressed and then added
    for the effective utilization of AppEngine db.

    Args:
      version_id: Version id number.
      page_map: Map containing mapping of page_url; page_content for all the
      pages that need to be added to Page table.

    Returns:
      Count of the pages added to db successfully.
      -1 if error.
    """
    # Insert all the pages into db
    # Create parent for all these pages
    page_parent = self.GetParent(version_id)
    if page_parent:
      pass
    else:
      logging.error('Error while getting pageparent')
      return -1
    # Initialize
    count = 0
    pages = []
    # For every page in page_map
    for url, value in page_map.iteritems():
      logging.debug('Adding page: %s to db', url)
      page = Page(parent=page_parent)
      page.version_id = version_id
      page.html_url = url
      page.html_content = zlib.compress(value)
      pages.append(page)
      count += 1
    if count > 0:
      db.put(pages)
    return count

  def AddPages(self, page_map, version_id):
    """Adds pages specified in page_map to db.

    It adds the pages specified in the page map in db for the specified
    version.

    Args:
      page_map: Map containing mapping of page_url; page_content for all the
      pages that need to be added to Page table.
      version_id: Version id number.

    Returns:
      True: If pages added successfully to db. False otherwise.
    """
    try:
      if not version_id:
        logging.error('Version not specified while adding pages')
        return False
      logging.info('Adding pages to version %d', version_id)
      pages_added = self._AddPages(version_id, page_map)
      logging.info('%d page(s) added successfully', pages_added)
      return True
    except db.Error, e:
      logging.error(e)
      raise digital_content_wizard_exceptions.DbException(
          'Error while adding HTML Pages to DB.\n' + str(e))
    except runtime.DeadlineExceededError, e:
      logging.error(e)
      raise digital_content_wizard_exceptions.DbException(
          'Error while adding HTML Pages to DB.\n' + str(e))

  def RemovePages(self, version_id):
    """Removes pages from Page table in batch of 300.

    This is done as not all the pages can be removed at one go due to response
    time restriction.

    Args:
      version_id: Version id number.

    Returns:
      True if no pages of specified versionare remaining to delete; False if
      pages are found.
    """
    query = Page.gql('WHERE version_id = :1', version_id)
    docs = query.fetch(constants.NO_OF_ROWS_IN_SET)
    if docs:
      db.delete(list(docs))
      return False
    return True

  def GetPage(self, html_url, version_id=None):
    """Returns the page content for the specified page url.

    Returns the html content for the requested url and the requested version
    id.  If version id is not specified, the content is fetched from the
    currently published version.

    Args:
      html_url: Url string of the page.
      version_id: Version id number.

    Returns:
      Page content string.
    """
    try:
      if version_id is None:
        version_id = self.GetPublishedVersion()
        if version_id is None:
          logging.error('No published version found')
          return None
      logging.debug(
          'Trying to get page ' + html_url + ' version ' + str(version_id))
      query_str = 'SELECT * FROM Page WHERE version_id = :1'
      query_str += ' AND html_url = :2'
      query = db.GqlQuery(query_str, version_id, html_url)
      result = query.fetch(1)
      if result:
        logging.info('page ' + html_url + ' retrieved successfully')
        return zlib.decompress(str(result[0].html_content))
      return None
    except db.Error, e:
      logging.error(e)
      return None

###########################################################################
#                    PageParent Related Functions
###########################################################################
  def CreateParent(self, version_id):
    """Creates PageParent object for specified version id.

    PageParent is needed to have all the Page objects under one parent.

    Args:
      version_id: Version id number.

    Returns:
      Object of PageParent.
    """
    page_parent = PageParent()
    page_parent.version_id = version_id
    page_parent.put()
    return page_parent

  def GetParent(self, version_id):
    """Returns PageParent object for the specified version id.

    Args:
      version_id: Version id number.

    Returns:
      PageParent object for the specified version id.
    """
    try:
      query = PageParent.gql('WHERE version_id = :1', version_id)
      result = query.fetch(1)
      if result:
        return result[0]
      else:
        page_parent = self.CreateParent(version_id)
        return page_parent
    except db.Error:
      logging.error(
          'Error while getting the parent for the version %d',
          version_id)
      return None

  def RemoveParent(self, version_id):
    """Removes the PageParent object for the specified version id.

    Args:
      version_id: Version id number.
    """
    query = PageParent.gql('WHERE version_id = :1', version_id)
    result = query.fetch(1)
    if result:
      result[0].delete()
      logging.info('Deleted from PageParent')

#######################################################################
#                      Metadata Related Functions
#######################################################################
  def CreateMetadata(self, row_id, document_id, version_id,
                     first_sorting_column, second_sorting_column,
                     third_sorting_column, title, rest_metadata, page_parent):
    """Creates Metadata object.

    Args:
      row_id: Row id number for the global data.
      document_id: Id string of the data object.
      version_id: Version id number.
      first_sorting_column: String value of sorting column 1.
      second_sorting_column: String value of sorting column 2.
      third_sorting_column: String value of sorting column 3.
      title: Title string.
      rest_metadata: String containing tab separated values for rest of the
      metadata columns.
      page_parent: PageParent object.

    Returns:
      Metadata object with all values set properly.
    """
    metadata = Metadata(parent=page_parent)
    metadata.row_id = row_id
    metadata.id = document_id
    metadata.version_id = version_id
    metadata.sorting_column1 = first_sorting_column
    metadata.sorting_column2 = second_sorting_column
    metadata.sorting_column3 = third_sorting_column
    metadata.title = title
    metadata.rest_metadata = db.Text(rest_metadata)
    return metadata

  def AddBulkMetadata(self, rows):
    """Add multiple metadata objects into Metadata table.

    Args:
      rows: Array of MetaData objects.
    """
    try:
      logging.debug('adding ' + str(len(rows)) + ' rows to metadata')
      if rows:
        start_time = time.time()
        self.AddToDb(rows)
        end_time = time.time()
        logging.debug('time taken by db to put metadata:')
        logging.debug((end_time - start_time))
    except db.Error, e:
      raise digital_content_wizard_exceptions.DbException(
          'Error while adding metadata into DB.\n' + str(e))

  def GetMetadata(self, version_id):
    """Returns array of Metadata objects for the specified version.

    Args:
      version_id: Version id number.
    Returns:
      Array of Metadata objects if found; None otherwise.
    """
    try:
      query = Metadata.gql('WHERE version_id=:1 AND row_id > 0', version_id)
      if query:
        return query
      logging.error('No metadata found for the version %d', version_id)
      return None
    except db.Error, e:
      logging.error(e)
      raise digital_content_wizard_exceptions.DbException(
          'Error while getting metadata from DB.\n' + str(e))

  def GetDisplayFlags(self, version_id):
    """Returns the header row containing display flags.

    Args:
      version_id: Version id number.
    Returns:
      Array of display flag values.
    """
    try:
      query = Metadata.gql('WHERE version_id=:1 AND row_id=-1', version_id)
      result = query.fetch(1)
      if result:
        display_flags = result[0].rest_metadata.split('\t')
        return display_flags[len(constants.PAGE_PROPERTIES):]
      else:
        logging.error('No header row having display flags')
        return None
    except db.Error, e:
      logging.error(e)
      return None

  def GetMetadataById(self, version_id, metadata_id):
    """Returns array of Metadata objects for the specified version and id.

    Args:
      version_id: Version id number.
      metadata_id: String id.
    Returns:
      Metadata object with the specified id and version; None otherwise.
    """
    query = Metadata.gql('WHERE version_id=:1 AND id=:2', version_id,
                         metadata_id)
    result = query.fetch(1)
    if result:
      metadata_values = []
      metadata_values.append(result[0].id)
      metadata_values.append(result[0].sorting_column1)
      metadata_values.append(result[0].sorting_column2)
      metadata_values.append(result[0].sorting_column3)
      metadata_values.append(result[0].title)
      metadata_values.extend(result[0].rest_metadata.split('\t'))
      return metadata_values
    logging.warning('No metadata found for the id %s and version %d',
                    metadata_id, version_id)
    return None

  def GetMetadataByRowNumber(self, row_number, version_id):
    """Returns Metadata object for specified row_number and version.

    Args:
      row_number: Row id number.
      version_id: Version id number.

    Returns:
      Metadata object if found; None otherwise.
    """
    query = Metadata.gql('WHERE row_id=:1 AND version_id=:2', row_number,
                         version_id)
    result = query.fetch(1)
    if result:
      return result[0]
    return None

  def UpdateMetadataIdByRowNumber(self, document_id, row_number, version_id):
    """Fetches the Metadata row from its row id and updates the id.

    Args:
      document_id: New id string.
      row_number: Row id number.
      version_id: Version id number.

    Returns:
      True: If id is updated successfully.
      False: If Metadata row with specified row number is not found.
    """
    metadata = self.GetMetadataByRowNumber(row_number, version_id)
    if metadata:
      metadata.id = document_id
      metadata.put()
      return True
    else:
      logging.error('Error while updating id ' + document_id)
      logging.error('metadata not found')
      return False

  def RemoveMetadata(self, version_id=None):
    """Removes Metadata rows in batch of 100.

    Args:
      version_id: Id number of the version whose metadata is to be removed.

    Returns:
      True: No metadata row for the specified version is remaining.
      False: Otherwise.
    """
    if version_id:
      query = Metadata.gql('WHERE version_id = :1', version_id)
      start_time = time.time()
      result = query.fetch(constants.NO_OF_ROWS_IN_SET)
      if result:
        logging.debug('deleting ' + str(result[0].row_id))
        db.delete(list(result))
        end_time = time.time()
        logging.debug('time taken to delete all the metadata rows:')
        logging.debug((end_time - start_time))
        return False
      return True

#########################################################
#             Global Data Related Functions
#########################################################
  def CreateGlobalData(self, row_id, data_type, value, variable_name,
                       version_id, page_parent):
    """Creates GlobalData object.

    Args:
      row_id: Row id number for the global data.
      data_type: Type number of the variable.
      value: String value of this global data variable.
      variable_name: String variable name used to refer to this attribute.
      version_id: Version id number.
      page_parent: PageParent object.

    Returns:
      Globaldata object with all values set properly.
    """
    globaldata = GlobalData(parent=page_parent)
    globaldata.row_id = row_id
    globaldata.type = data_type
    globaldata.value = db.Text(value, encoding=constants.ENCODING)
    globaldata.variable = variable_name
    globaldata.version_id = version_id
    return globaldata

  def AddBulkGlobalData(self, rows):
    """Adds multiple GlobalData objects into GlobalData table.

    Args:
      rows: Array of GlobalData objects.
    """
    logging.debug('adding ' + str(len(rows)) + ' rows to global data')
    if rows:
      start_time = time.time()
      self.AddToDb(rows)
      end_time = time.time()
      logging.debug('time taken by db to put global data:')
      logging.debug((end_time -start_time))

  def GetGlobalDataByVersion(self, version_id):
    """Returns GlobalData having the specified version id.

    Args:
      version_id: Version id number.
    Returns:
      Array of Globaldata objects having specified version id.
    """
    query = GlobalData.gql('WHERE version_id=:1', version_id)
    if query:
      return query
    else:
      logging.error('Image with this variable name not found')
      return None

  def GetGlobalDataByVariable(self, variable_name, version_id):
    """Returns GlobalData object having the specified variable name.

    Args:
      variable_name: Variable name string.
      version_id: Version id number.
    Returns:
      Globaldata object having specified variable name.
    """
    query = GlobalData.gql('WHERE version_id=:1 AND variable=:2', version_id,
                           variable_name)
    result = query.fetch(1)
    if result:
      return result[0]
    else:
      logging.error('Global data with this variable name not found')
      return None

  def RemoveGlobalData(self, version_id):
    """Removes all the GlobalData rows for the specified version id.

    Args:
      version_id: Version id number.
    """
    if version_id:
      query = GlobalData.gql('WHERE version_id = :1', version_id)
      start_time = time.time()
      rows = list(query)
      if rows:
        db.delete(rows)
      end_time = time.time()
      logging.debug('time taken to delete all the global data rows:')
      logging.debug((end_time - start_time))
